#include <asm/system.h>
#include <cnix/errno.h>
#include <cnix/string.h>
#include <cnix/fs.h>
#include <cnix/mm.h>
#include <cnix/kernel.h>
#include <cnix/modconfig.h>

extern struct filesystem ext2_filesystem;
extern struct filesystem minix_filesystem;
extern struct filesystem iso9660_filesystem;

struct filesystem * filesystem_list[] = {
	&ext2_filesystem,
	&minix_filesystem,
#ifndef ISO9660_MODULE
	&iso9660_filesystem,
#else
	NULL,
#endif
};

#define NR_FILESYSTEM (sizeof(filesystem_list) / sizeof(struct filesystem *))

static BOOLEAN check_fs_busy(struct filesystem *fs);

BOOLEAN register_filesystem(struct filesystem * fs)
{
	int i;

	for(i = 0; i < NR_FILESYSTEM; i++){
		if(!filesystem_list[i]){
			filesystem_list[i] = fs;
			return TRUE;
		}
	}

	return FALSE;
}

BOOLEAN unregister_filesystem(struct filesystem *fs)
{
	int i;

	for (i = 0; i < NR_FILESYSTEM; i++)
	{
		if (filesystem_list[i] == fs)
		{
			if (check_fs_busy(fs))
			{
				printk("fs is busy now!\n");
				return FALSE;
			}
			
			filesystem_list[i] = NULL;
			return TRUE;
		}
	}

	return FALSE;
}

#define NR_SUPERBLOCK 10

static struct super_block sblist[NR_SUPERBLOCK];

static BOOLEAN check_fs_busy(struct filesystem *fs)
{
	int i;
	struct super_block *sb;
	
loop:
	for(i = 0; i < NR_SUPERBLOCK; i++)
	{	
		sb = &sblist[i];
		
		// everything could be changed after being waked up
		if(sb->s_flags & LOCK_S)
		{
			sb->s_flags |= WANT_S;
			sleepon(&sb->s_wait);

			goto loop;
		}

		if ((sb->s_filesystem == fs) && (sb->s_count > 0))
		{
			return TRUE;
		}
	}

	// no other use the filesystem fs
	return FALSE;
}

void init_super(void)
{
	int i;
	
	for(i = 0; i < NR_SUPERBLOCK; i++){
		memset(&sblist[i], 0, sizeof(struct super_block));
		sblist[i].s_dev = NODEV;
		sblist[i].s_count = 0;
	}
}

void sfree(struct super_block * sb)
{
	sb->s_count--;
	if(!sb->s_count){
		if(!sb->s_data)
			DIE("BUG: cannot happen\n");

		sb->s_filesystem->free(sb);
		sb->s_dev = NODEV;
	}
}

struct super_block * sread(dev_t dev)
{
	int i;
	struct super_block * sb;

	sb = NULL;
loop:
	for(i = 0; i < NR_SUPERBLOCK; i++){
		if(sblist[i].s_dev == dev){
			// everything could be changed after being waked up
			if(sblist[i].s_flags & LOCK_S){
				sblist[i].s_flags |= WANT_S;
				sleepon(&sblist[i].s_wait);

				goto loop;
			}

			return &sblist[i];
		}else if(!sb && (!sblist[i].s_count))
			sb = &sblist[i];
	}

	if(!sb){
		PRINTK("super block not enough\n");
		return NULL;
	}

	// may sleep
	sb->s_flags = LOCK_S;

	// default is writable
	sb->s_roflag = 0;

	for(i = 0; i < NR_FILESYSTEM; i++){
		if(!filesystem_list[i])
			continue;

		if(filesystem_list[i]->init(sb, dev)){
			if(!sb->s_data || !sb->s_filesystem)
				DIE("BUG: cannot happen\n");

			sb->s_flags &= ~LOCK_S;

			sb->s_rooti		= NULL;
			sb->s_mounti		= NULL;
			sb->s_count		= 1;
			sb->s_wait		= NULL;
			sb->s_bitop_wait	= NULL;
			sb->s_dev		= dev;	

			if(sb->s_flags & WANT_S){
				sb->s_flags &= ~WANT_S;
				wakeup(&sb->s_wait);
			}

			return sb;
		}
	}

	sb->s_flags &= ~LOCK_S;
	if(sb->s_flags & WANT_S){
		sb->s_flags &= ~WANT_S;
		wakeup(&sb->s_wait);
	}

	return NULL;
}

// get the super block of dev
struct super_block * get_sb(dev_t dev)
{
	int i;

	if (dev == NODEV)
	{
		return NULL;
	}

	for (i = 0; i < NR_SUPERBLOCK; i++)
	{
		if (sblist[i].s_dev == dev)
		{
			return &sblist[i];
		}
	}

	return NULL;
}


